﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using CoreEx.Common.Extensions;
using CoreEx.Common.Linq;
using CoreEx.Database.Metadata;
using CoreEx.Database.Query;
using LinFu.IoC.Configuration;

namespace CoreEx.Database.Linq.Implementation
{
    [Implements(typeof(IDervidedTableTranslator))]
    public class DerivedTableTranslator : JoinBasedExpressionTranslator, IDervidedTableTranslator
    {
        private MemberExpression _enumerableExpression;
        private IProjectionTarget_old _outerProjectionTarget;

        public DerivedTableTranslator(string connectionName) : base(connectionName)
        {
        }

        public ITranslateResult_old Translate(MemberExpression memberExpression, IProjectionTarget_old outerProjectionTarget)
        {
            _outerProjectionTarget = outerProjectionTarget;
            _translateResult = new TranslateResult_old();       
            _translateResult.DbExpression = Visit(memberExpression.Expression);            
            CreateFinalQuery();
            ((ISelectExpression)_translateResult.DbExpression).ProjectionExpression =
                GetProjectionExpression(memberExpression);                                                
            return _translateResult;
        }

        private IExpression GetProjectionExpression(MemberExpression memberExpression)
        {
            var property = (PropertyInfo)memberExpression.Member;
            var typeMappingInfo = _translateResult.ProjectionTargets[property.DeclaringType].TypeMappingInfo;
            var alias = _translateResult.ProjectionTargets.First().Value.Alias;
            
            return ExpressionFactory.Column(alias, typeMappingInfo.ColumnMappings[property].ColumnInfo.ColumnName);
        }


        protected override IExpression VisitMemberAccess(MemberExpression memberExpression)
        {
            if (memberExpression.Member.GetMemberType().IsEnumerable())
            {
                var joinsequence = GetJoinSequence(memberExpression).ToList();
                
                
                var rootType = joinsequence.Last().Member.GetMemberType().GetGenericElementType();
                var query = CreateRootQuery(rootType);

                
                ((ISelectExpression) query).IsSubQuery = true;                
                _enumerableExpression = memberExpression; 
               
                CreateJoins2();

                var property = (PropertyInfo) joinsequence.Last().Member;
                var foreignKeyInfo = _outerProjectionTarget.TypeMappingInfo.ColumnMappings[property].ForeignKeyInfo;
                IExpression criteriaExpression = null;
                var thisAlias = _translateResult.ProjectionTargets.First().Value.Alias;
                var outerAlias = _outerProjectionTarget.Alias;
                foreach (var foreignKeyColumnInfo in foreignKeyInfo.Columns)
                {
                    IExpression equalsExpression = ExpressionFactory.Column(thisAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName) ==
                                                   ExpressionFactory.Column(outerAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName);
                    if (criteriaExpression == null)
                        criteriaExpression = equalsExpression;
                    else
                        criteriaExpression = (ExpressionBase)criteriaExpression & (ExpressionBase)equalsExpression;
                }

                var selectQuery = (SelectQueryExpression)_translateResult.ProjectionTargets.First().Value.DbExpression;
                selectQuery.Where(criteriaExpression);
                return query;
                
            }
            
            return base.VisitMemberAccess(memberExpression);
        }

        private IEnumerable<MemberExpression> GetJoinSequence(MemberExpression memberExpression)
        {
            IList<MemberExpression> memberExpressions = new List<MemberExpression>();
            Expression expression = memberExpression;
            while (expression is MemberExpression)
            {                
                memberExpressions.Add((MemberExpression)expression);
                expression = ((MemberExpression)expression).Expression;
            }

            return memberExpressions;
        }

        private void CreateJoins2()
        {
            var joinSequence = GetJoinSequence(_enumerableExpression);
            
            var memberExpressions = joinSequence.Reverse().Skip(1);
            foreach (var memberExpression in memberExpressions)
            {
                var property = (PropertyInfo) memberExpression.Member;

                var outerProjectionTarget = GetProjectionTarget(memberExpression.Expression);
                //This means that we have reached the end of the join sequence
                //The containing expression is now either a memberExpression or a parameter expression
                if (property.PropertyType.IsEnumerable())
                {                                        
                    var propertyType = property.PropertyType.GetGenericElementType();
                    if (_translateResult.ProjectionTargets.ContainsKey(propertyType))
                        continue;
                    var thisAlias = GetMemberAlias(propertyType);
                    var outerAlias = outerProjectionTarget.Alias;
                    var foreignKeyInfo = outerProjectionTarget.TypeMappingInfo.ColumnMappings[property].ForeignKeyInfo;
                    IExpression criteriaExpression = null;
                    foreach (var foreignKeyColumnInfo in foreignKeyInfo.Columns)
                    {
                        IExpression equalsExpression = ExpressionFactory.Column(thisAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName) ==
                                                       ExpressionFactory.Column(outerAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName);
                        if (criteriaExpression == null)
                            criteriaExpression = equalsExpression;
                        else
                            criteriaExpression = (ExpressionBase)criteriaExpression & (ExpressionBase)equalsExpression;
                    }
                    IExpression joinExpression;
                    joinExpression = ExpressionFactory.Join(JoinType.LeftOuter,
                                                           ExpressionFactory.Table(
                                                               foreignKeyInfo.PrimaryTable.TableName, thisAlias),
                                                           criteriaExpression);
                    Projectiontarget_old joinProjectionTarget = new Projectiontarget_old();
                    joinProjectionTarget.Alias = thisAlias;
                    joinProjectionTarget.Member = property.PropertyType.GetGenericElementType();
                    joinProjectionTarget.TypeMappingInfo = MappingRepository[propertyType];
                    joinProjectionTarget.DbExpression = joinExpression;
                    joinProjectionTarget.Parent = outerProjectionTarget;
                    outerProjectionTarget.Children.Add(joinProjectionTarget);
                    _translateResult.ProjectionTargets.Add(propertyType, joinProjectionTarget);
                }
                else
                {
                    if (_translateResult.ProjectionTargets.ContainsKey(property))
                        continue;
                    var foreignKeyInfo = outerProjectionTarget.TypeMappingInfo.ColumnMappings[property].ForeignKeyInfo;
                    var outerAlias = outerProjectionTarget.Alias;
                    var thisAlias = GetMemberAlias(property);
                    IExpression criteriaExpression = null;
                    foreach (var foreignKeyColumnInfo in foreignKeyInfo.Columns)
                    {
                        IExpression equalsExpression = ExpressionFactory.Column(thisAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName) ==
                                                       ExpressionFactory.Column(outerAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName);
                        if (criteriaExpression == null)
                            criteriaExpression = equalsExpression;
                        else
                            criteriaExpression = (ExpressionBase)criteriaExpression & (ExpressionBase)equalsExpression;
                    }
                    IExpression joinExpression;
                    joinExpression = ExpressionFactory.Join(JoinType.LeftOuter,
                                                           ExpressionFactory.Table(
                                                               foreignKeyInfo.PrimaryTable.TableName, thisAlias),
                                                           criteriaExpression);

                    Projectiontarget_old joinProjectionTarget = new Projectiontarget_old();
                    joinProjectionTarget.Alias = thisAlias;
                    joinProjectionTarget.Member = property;
                    joinProjectionTarget.TypeMappingInfo = MappingRepository[property.PropertyType];
                    joinProjectionTarget.DbExpression = joinExpression;
                    joinProjectionTarget.Parent = outerProjectionTarget;
                    outerProjectionTarget.Children.Add(joinProjectionTarget);
                    _translateResult.ProjectionTargets.Add(property, joinProjectionTarget);
                }


            }

            


        }

        private IProjectionTarget_old GetProjectionTarget(Expression expression)
        {
            if (_translateResult.ProjectionTargets.ContainsKey(((MemberExpression)expression).Member))
                return _translateResult.ProjectionTargets[((MemberExpression)expression).Member];
            
            return _translateResult.ProjectionTargets[expression.Type];            
        }


        private void CreateJoins()
        {
            var memberExpressions = GetJoinSequence(_enumerableExpression);
            foreach (var memberExpression in memberExpressions)
            {
                if (memberExpression.Expression.NodeType != ExpressionType.Parameter)
                    CreateJoin(memberExpression);
                else
                {
                    var outerAlias = _outerProjectionTarget.Alias;
                    var thisAlias = _translateResult.ProjectionTargets.First().Value.Alias;
                    var typeMappingInfo = MappingRepository[(memberExpression.Expression).Type];
                    var property = (PropertyInfo)memberExpression.Member;
                    var foreignKeyInfo = typeMappingInfo.ColumnMappings[property].ForeignKeyInfo;
                    IExpression criteriaExpression = null;


                    foreach (var foreignKeyColumnInfo in foreignKeyInfo.Columns)
                    {
                        IExpression equalsExpression = ExpressionFactory.Column(thisAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName) ==
                                                       ExpressionFactory.Column(outerAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName);
                        if (criteriaExpression == null)
                            criteriaExpression = equalsExpression;
                        else
                            criteriaExpression = (ExpressionBase)criteriaExpression & (ExpressionBase)equalsExpression;
                    }

                    var selectQuery = (SelectQueryExpression)_translateResult.ProjectionTargets.First().Value.DbExpression;
                    selectQuery.Where(criteriaExpression);
                }
            }    
        }


        public void CreateJoin(MemberExpression memberExpression)
        {
            var property = (PropertyInfo) memberExpression.Member;            
            var typeMappingInfo = MappingRepository[property.DeclaringType];
            var propertyMapping = typeMappingInfo.ColumnMappings[property];
            var foreignKeyInfo = typeMappingInfo.ColumnMappings[property].ForeignKeyInfo;            
           
            if (propertyMapping.RelationType == CardinalityType.OneToMany)
            {                
                var outerAlias = _translateResult.ProjectionTargets.First().Value.Alias; 
                var thisAlias = GetMemberAlias(property.DeclaringType);

                IExpression criteriaExpression = null;   
                
                foreach (var foreignKeyColumnInfo in foreignKeyInfo.Columns)
                {
                    IExpression equalsExpression = ExpressionFactory.Column(thisAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName) ==
                                                   ExpressionFactory.Column(outerAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName);
                    if (criteriaExpression == null)
                        criteriaExpression = equalsExpression;
                    else
                        criteriaExpression = (ExpressionBase)criteriaExpression & (ExpressionBase)equalsExpression;
                }

                var selectQuery = (SelectQueryExpression) _translateResult.ProjectionTargets.First().Value.DbExpression;
                selectQuery.LeftOuterJoin(ExpressionFactory.Table(foreignKeyInfo.PrimaryTable.TableName, thisAlias),
                                          criteriaExpression);                
            }
            else
            {
                var outerAlias = GetMemberAlias(property.PropertyType);
                if (_translateResult.ProjectionTargets.ContainsKey(property.DeclaringType))
                    return;
                
                var thisAlias = GetMemberAlias(property.DeclaringType);

                IExpression criteriaExpression = null;

                foreach (var foreignKeyColumnInfo in foreignKeyInfo.Columns)
                {
                    IExpression equalsExpression = ExpressionFactory.Column(thisAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName) ==
                                                   ExpressionFactory.Column(outerAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName);
                    if (criteriaExpression == null)
                        criteriaExpression = equalsExpression;
                    else
                        criteriaExpression = (ExpressionBase)criteriaExpression & (ExpressionBase)equalsExpression;
                }

                var selectQuery = (SelectQueryExpression)_translateResult.ProjectionTargets.First().Value.DbExpression;
                selectQuery.LeftOuterJoin(ExpressionFactory.Table(foreignKeyInfo.Table.TableName, outerAlias),
                                          criteriaExpression);                
            }
        }

       




        


    }
}
